#include "utils/spsc_queue.h"

namespace utils {
    template<typename T, size_t Capacity>
    SPSCQueue<T, Capacity>::SPSCQueue() : m_head(0), m_tail(0) {
        static_assert(Capacity >= 2, "Capacity must be at least 2");
        /// Do not use default constructor, for example Packet()
        LOG_DEBUG("SPSCQueue created with capacity: " << Capacity);
    }

    template<typename T, size_t Capacity>
    bool SPSCQueue<T, Capacity>::try_push(T&& item) {
        /// In consumer view
        const size_t current_tail = m_tail.load(std::memory_order_relaxed);
        const size_t next_tail = next_index(current_tail);

        /// Check if the queue is full
        /// Use 'acquire' to read the head, ensuring visibility of the consumer's latest progress
        const size_t current_head = m_head.load(std::memory_order_acquire);

        if (next_tail == current_head) {
            /// The queue is full
            m_push_failures.fetch_add(1, std::memory_order_relaxed);
            return false;
        }

        /// The queue is not full
        /// Use move construction to avoid copying, and in-place construction to ensure exception safety
        new (&m_buffer[current_tail]) T(std::move(item));

        /// Update tail, use 'release' to ensure that buffer writes are visible to the consumer
        m_tail.store(next_tail, std::memory_order_release);

        /// Update statistics
        m_push_count.fetch_add(1, std::memory_order_relaxed);

        return true;
    }

    template<typename T, size_t Capacity>
    bool SPSCQueue<T, Capacity>::try_pop(T& item) {
        /// In producer view
        const size_t current_head = m_head.load(std::memory_order_relaxed);

        /// Check if the queue is empty
        /// Use 'acquire' to read the tail, ensuring visibility of the producer's latest progress
        const size_t current_tail = m_tail.load(std::memory_order_acquire);

        if (current_head == current_tail) {
            /// The queue is empty
            m_pop_failures.fetch_add(1, std::memory_order_relaxed);
            return false;
        }

        /// The queue is not empty
        /// Use move assignment to avoid unnecessary copying
        T& element = *reinterpret_cast<T*>(&m_buffer[current_head]);
        item = std::move(element);

        /// Destroy the element in the buffer
        element.~T();

        /// Update head, use 'release' to ensure that element dequeues are visible to the producer
        m_head.store(next_index(current_head), std::memory_order_release);

        /// Update statistics
        m_pop_count.fetch_add(1, std::memory_order_relaxed);

        return true;
    }

    template<typename T, size_t Capacity>
    size_t SPSCQueue<T, Capacity>::push_bulk(T* items, size_t count) {
        if (count == 0) return 0;

        /// Get current state
        const size_t current_tail = m_tail.load(std::memory_order_relaxed);
        const size_t current_head = m_head.load(std::memory_order_acquire);

        /// Calculate available space
        size_t available;
        if (current_tail >= current_head) {
            available = Capacity - (current_tail - current_head);
        } else {
            available = current_head - current_tail;
        }

        /// When the available equals 1, the ring buffer is considered full
        if (available <= 1) {
            m_push_failures.fetch_add(1, std::memory_order_relaxed);
            return 0;
        }

        /// Actual numbers
        size_t actual_count = std::min(count, available - 1);


        /// Batch copy elements
        size_t tail = current_tail;
        for (size_t i = 0; i < actual_count; ++i) {
            new (&m_buffer[tail]) T(std::move(items[i]));
            tail = next_index(tail);
        }

        /// Publish new elements
        m_tail.store(tail, std::memory_order_release);

        /// Update statistics
        m_push_count.fetch_add(actual_count, std::memory_order_relaxed);

        return actual_count;
    }

    template<typename T, size_t Capacity>
    size_t SPSCQueue<T, Capacity>::pop_bulk(T* items, size_t count) {
        if (count == 0) return 0;

        /// Get current state
        const size_t current_head = m_head.load(std::memory_order_relaxed);
        const size_t current_tail = m_tail.load(std::memory_order_acquire);

        /// Calculate queue size
        size_t size;
        if (current_tail >= current_head) {
            size = current_tail - current_head;
        } else {
            size = Capacity - (current_head - current_tail);
        }

        if (size == 1) {
            m_pop_failures.fetch_add(1, std::memory_order_relaxed);
            return 0;
        }

        /// Actual numbers
        size_t actual_count = std::min(count, size);


        /// Batch copy elements
        size_t head = current_head;
        for (size_t i = 0; i < actual_count; ++i) {
            T& element = *reinterpret_cast<T*>(&m_buffer[head]);
            items[i] = std::move(element);
            element.~T();
            head = next_index(head);
        }

        /// Update consumer position
        m_head.store(head, std::memory_order_release);

        /// Update statistics
        m_push_count.fetch_add(actual_count, std::memory_order_relaxed);

        return actual_count;
    }

    template<typename T, size_t Capacity>
    bool SPSCQueue<T, Capacity>::empty() const noexcept{
        /// Use 'relaxed', snapshot state check
        /// The result is not reliable enough to make decisions
        const size_t current_head = m_head.load(std::memory_order_relaxed);
        const size_t current_tail = m_tail.load(std::memory_order_relaxed);
        return current_head == current_tail;
    }

    template<typename T, size_t Capacity>
    size_t SPSCQueue<T, Capacity>::size() const noexcept {
        const size_t head = m_head.load(std::memory_order_acquire);
        const size_t tail = m_tail.load(std::memory_order_acquire);

        if (tail >= head) {
            return tail - head;
        } else {
            return Capacity - (head - tail);
        }
    }

    template<typename T, size_t Capacity>
    void SPSCQueue<T, Capacity>::clear() {
        size_t head = m_head.load(std::memory_order_relaxed);
        const size_t tail = m_tail.load(std::memory_order_relaxed);

        while (head != tail) {
            m_buffer[head].~T();
            head = next_index(head);
        }

        /// Reset index
        m_head.store(0, std::memory_order_relaxed);
        m_tail.store(0, std::memory_order_relaxed);

        LOG_DEBUG("SPSCQueue cleared");
    }

    template<typename T, size_t Capacity>
    typename SPSCQueue<T, Capacity>::Stats SPSCQueue<T, Capacity>::get_stats() const {
        Stats stats;
        stats.push_count = m_push_count.load(std::memory_order_relaxed);
        stats.pop_count = m_pop_count.load(std::memory_order_relaxed);
        stats.push_failures = m_push_failures.load(std::memory_order_relaxed);
        stats.pop_failures = m_pop_failures.load(std::memory_order_relaxed);
        return stats;
    }
} /// namespace utils